/* TrackerInfo - Holds information returned by a tracker, mainly the peer list.
   Copyright (C) 2003 Mark J. Wielaard

   This file is part of Snark.
   
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.
 
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
 
   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

package org.klomp.snark;

import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.InetAddress;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.HashSet;
import java.util.TreeSet;

import org.klomp.snark.bencode.*;

public class TrackerInfo {
	private final String failure_reason;
	private final int interval;
	private final Set peers;

	public TrackerInfo(InputStream in, byte[] my_id, MetaInfo metainfo) throws IOException {
		this(new BDecoder(in), my_id, metainfo);
	}

	public TrackerInfo(BDecoder be, byte[] my_id, MetaInfo metainfo) throws IOException {
		this(be.bdecodeMap().getMap(), my_id, metainfo);
	}

	public TrackerInfo(Map m, byte[] my_id, MetaInfo metainfo) throws IOException {
		BEValue reason = (BEValue)m.get("failure reason");
		if(reason != null) {
			failure_reason = reason.getString();
			interval = -1;
			peers = null;
		} else {
			failure_reason = null;
			BEValue beInterval = (BEValue)m.get("interval");
			if(beInterval == null)
				throw new InvalidBEncodingException("No interval given");
			else
				interval = beInterval.getInt();
			BEValue bePeers = (BEValue)m.get("peers");
			if(bePeers == null)
				throw new InvalidBEncodingException("No peer list");
			else {
				Set peersTemp = new HashSet();
				try {
					peersTemp = getPeers(bePeers.getList(), my_id, metainfo);
				} catch(InvalidBEncodingException e) {
					//Try compact mode
					byte[] bytes = bePeers.getBytes();
					
					//Check length is a multiple of 6
					if(bytes.length % 6 == 0) {
						Random rnd = new Random();
						for(int x = 0;x < bytes.length; x += 6) {
							Snark.debug((bytes[x] & 0xff) + "." + (bytes[x+1] & 0xff) + "." + (bytes[x+2] & 0xff) + "." + (bytes[x+3] & 0xff) + ":" + ((bytes[x+4] << 8 | bytes[x+5]) & 0xffff), Snark.WARNING);
							
							byte[] ip = {bytes[x], bytes[x+1], bytes[x+2], bytes[x+3]};
							int port = (bytes[x+4] << 8 | bytes[x+5]) & 0xffff;
							
							InetAddress inetAddr = InetAddress.getByAddress(ip);
							
							byte[] rndPeerId = new byte[20];
							rnd.nextBytes(rndPeerId);
							
							PeerID peerId = new PeerID(rndPeerId, inetAddr, port);
							Peer peer = new Peer(peerId, my_id, metainfo);
							
							peersTemp.add(peer);
						}
					} else {
						Snark.debug("Failed to parse compact peers!  Bytes not a multiple of six!", Snark.ERROR);
					}
				}
				
				peers = peersTemp;
			}
		}
	}

	public static Set getPeers(InputStream in, byte[] my_id, MetaInfo metainfo) throws IOException {
		return getPeers(new BDecoder(in), my_id, metainfo);
	}

	public static Set getPeers(BDecoder be, byte[] my_id, MetaInfo metainfo) throws IOException {
		return getPeers(be.bdecodeList().getList(), my_id, metainfo);
	}

	public static Set getPeers(List l, byte[] my_id, MetaInfo metainfo) throws IOException {
		Set peers = new HashSet(l.size());

		Iterator it = l.iterator();
		while(it.hasNext()) {
			PeerID peerID = new PeerID(((BEValue)it.next()).getMap());
			peers.add(new Peer(peerID, my_id, metainfo));
		}

		return peers;
	}

	public Set getPeers() {
		return peers;
	}

	public String getFailureReason() {
		return failure_reason;
	}

	public int getInterval() {
		return interval;
	}

	public String toString() {
		if(failure_reason != null)
			return "TrackerInfo[FAILED: " + failure_reason + "]";
		else
			return "TrackerInfo[interval=" + interval + ", peers=" + peers + "]";
	}
}
